File: src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\CodeStyle\CodeStyleOption2`1.cs
Web Access
Project: src\src\CodeStyle\Core\Analyzers\Microsoft.CodeAnalysis.CodeStyle.csproj (Microsoft.CodeAnalysis.CodeStyle)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Xml.Linq;
using Microsoft.CodeAnalysis.Diagnostics;
 
namespace Microsoft.CodeAnalysis.CodeStyle;
 
/// <summary>
/// Internal representation of a code style option value. Should be used throughout Roslyn.
/// The internal values are translated to the public ones (ICodeStyleOption) at the public entry points.
/// </summary>
internal interface ICodeStyleOption2
{
    XElement ToXElement();
    object? Value { get; }
    NotificationOption2 Notification { get; }
    ICodeStyleOption2 WithValue(object value);
    ICodeStyleOption2 WithNotification(NotificationOption2 notification);
 
    /// <summary>
    /// Creates a new <see cref="ICodeStyleOption2"/> from a specified <paramref name="element"/>.
    /// </summary>
    /// <exception cref="Exception">
    /// The type of the serialized data does not match the type of <see cref="ICodeStyleOption2.Value"/> or the format of the serialized data is invalid.
    /// </exception>
    ICodeStyleOption2 FromXElement(XElement element);
}
 
internal static class CodeStyleOption2
{
    /// <remarks>
    /// When user preferences are not yet set for a style, we fall back to the default value.
    /// One such default(s), is that the feature is turned on, so that codegen consumes it,
    /// but with silent enforcement, so that the user is not prompted about their usage.
    /// </remarks>
    public static readonly CodeStyleOption2<bool> TrueWithSilentEnforcement = new(value: true, notification: NotificationOption2.Silent);
    public static readonly CodeStyleOption2<bool> FalseWithSilentEnforcement = new(value: false, notification: NotificationOption2.Silent);
    public static readonly CodeStyleOption2<bool> TrueWithSuggestionEnforcement = new(value: true, notification: NotificationOption2.Suggestion);
    public static readonly CodeStyleOption2<bool> FalseWithSuggestionEnforcement = new(value: false, notification: NotificationOption2.Suggestion);
 
    /// <summary>
    /// Use singletons for most common values.
    /// </summary>
    public static CodeStyleOption2<bool> GetCodeStyle(bool value, NotificationOption2 notification)
        => (value, notification.Severity) switch
        {
            (true, ReportDiagnostic.Hidden) => TrueWithSilentEnforcement,
            (true, ReportDiagnostic.Info) => TrueWithSuggestionEnforcement,
            (false, ReportDiagnostic.Hidden) => FalseWithSilentEnforcement,
            (false, ReportDiagnostic.Info) => FalseWithSuggestionEnforcement,
            _ => new(value, notification)
        };
}
 
/// <summary>
/// Represents a code style option and an associated notification option.  Supports
/// being instantiated with T as a <see cref="bool"/> or an <c>enum type</c>.
/// 
/// CodeStyleOption also has some basic support for migration a <see cref="bool"/> option
/// forward to an <c>enum type</c> option.  Specifically, if a previously serialized
/// bool-CodeStyleOption is then deserialized into an enum-CodeStyleOption then 'false' 
/// values will be migrated to have the 0-value of the enum, and 'true' values will be
/// migrated to have the 1-value of the enum.
/// 
/// Similarly, enum-type code options will serialize out in a way that is compatible with 
/// hosts that expect the value to be a boolean.  Specifically, if the enum value is 0 or 1
/// then those values will write back as false/true.
/// </summary>
[DataContract]
internal sealed partial class CodeStyleOption2<T>(T value, NotificationOption2 notification) : ICodeStyleOption2, IEquatable<CodeStyleOption2<T>?>
{
    public static readonly CodeStyleOption2<T> Default = new(default!, NotificationOption2.Silent);
 
    private const int SerializationVersion = 1;
 
    private const string XmlElement_CodeStyleOption = "CodeStyleOption";
    private const string XmlAttribute_SerializationVersion = "SerializationVersion";
    private const string XmlAttribute_Type = "Type";
    private const string XmlAttribute_Value = "Value";
    private const string XmlAttribute_DiagnosticSeverity = "DiagnosticSeverity";
 
    [DataMember(Order = 0)]
    public T Value { get; } = value;
 
    [DataMember(Order = 1)]
    public NotificationOption2 Notification { get; } = notification;
 
    object? ICodeStyleOption2.Value => this.Value;
    ICodeStyleOption2 ICodeStyleOption2.WithValue(object value) => WithValue((T)value);
    ICodeStyleOption2 ICodeStyleOption2.WithNotification(NotificationOption2 notification) => new CodeStyleOption2<T>(Value, notification);
 
    public CodeStyleOption2<T> WithValue(T value)
    {
        if (typeof(T) == typeof(bool))
        {
            var boolValue = (bool)(object)value!;
            if (boolValue == (bool)(object)Value!)
            {
                return this;
            }
 
            return (CodeStyleOption2<T>)(object)CodeStyleOption2.GetCodeStyle(boolValue, Notification);
        }
 
        return EqualityComparer<T>.Default.Equals(value, Value) ? this : new CodeStyleOption2<T>(value, Notification);
    }
 
    private int EnumValueAsInt32 => (int)(object)Value!;
 
    public XElement ToXElement()
        => new(XmlElement_CodeStyleOption, // Ensure that we use "CodeStyleOption" as the name for back compat.
            new XAttribute(XmlAttribute_SerializationVersion, SerializationVersion),
            new XAttribute(XmlAttribute_Type, GetTypeNameForSerialization()),
            new XAttribute(XmlAttribute_Value, GetValueForSerialization()),
            new XAttribute(XmlAttribute_DiagnosticSeverity, Notification.Severity.ToDiagnosticSeverity() ?? DiagnosticSeverity.Hidden));
 
    private object GetValueForSerialization()
    {
        if (typeof(T) == typeof(string))
        {
            return Value!;
        }
        else if (typeof(T) == typeof(bool))
        {
            return Value!;
        }
        else if (IsZeroOrOneValueOfEnum())
        {
            return EnumValueAsInt32 == 1;
        }
        else
        {
            return EnumValueAsInt32;
        }
    }
 
    private string GetTypeNameForSerialization()
    {
        if (typeof(T) == typeof(string))
        {
            return nameof(String);
        }
 
        if (typeof(T) == typeof(bool) || IsZeroOrOneValueOfEnum())
        {
            return nameof(Boolean);
        }
        else
        {
            return nameof(Int32);
        }
    }
 
    private bool IsZeroOrOneValueOfEnum()
    {
        var intVal = EnumValueAsInt32;
        return intVal is 0 or 1;
    }
 
    ICodeStyleOption2 ICodeStyleOption2.FromXElement(XElement element)
        => FromXElement(element);
 
    public static CodeStyleOption2<T> FromXElement(XElement element)
    {
        var typeAttribute = element.Attribute(XmlAttribute_Type);
        var valueAttribute = element.Attribute(XmlAttribute_Value);
        var severityAttribute = element.Attribute(XmlAttribute_DiagnosticSeverity);
        var version = (int?)element.Attribute(XmlAttribute_SerializationVersion);
 
        if (typeAttribute == null || valueAttribute == null || severityAttribute == null)
        {
            // data from storage is corrupt, or nothing has been stored yet.
            return Default;
        }
 
        if (version != SerializationVersion)
        {
            return Default;
        }
 
        var parser = GetParser(typeAttribute.Value);
        var value = parser(valueAttribute.Value);
        var severity = (DiagnosticSeverity)Enum.Parse(typeof(DiagnosticSeverity), severityAttribute.Value);
 
        return new CodeStyleOption2<T>(value, severity switch
        {
            DiagnosticSeverity.Hidden => NotificationOption2.Silent,
            DiagnosticSeverity.Info => NotificationOption2.Suggestion,
            DiagnosticSeverity.Warning => NotificationOption2.Warning,
            DiagnosticSeverity.Error => NotificationOption2.Error,
            _ => throw new ArgumentException(nameof(element)),
        });
    }
 
    private static Func<string, T> GetParser(string type)
        => type switch
        {
            nameof(Boolean) =>
                // Try to map a boolean value.  Either map it to true/false if we're a 
                // CodeStyleOption<bool> or map it to the 0 or 1 value for an enum if we're
                // a CodeStyleOption<SomeEnumType>.
                v => Convert(bool.Parse(v)),
            nameof(Int32) => v => Convert(int.Parse(v)),
            nameof(String) => v => (T)(object)v,
            _ => throw new ArgumentException(nameof(type)),
        };
 
    private static T Convert(bool b)
    {
        // If we had a bool and we wanted a bool, then just return this value.
        if (typeof(T) == typeof(bool))
        {
            return (T)(object)b;
        }
 
        // Map booleans to the 1/0 value of the enum.
        return b ? (T)(object)1 : (T)(object)0;
    }
 
    private static T Convert(int i)
    {
        // We got an int, but we wanted a bool.  Map 0 to false, 1 to true, and anything else to default.
        if (typeof(T) == typeof(bool))
        {
            return (T)(object)(i == 1);
        }
 
        // If had an int and we wanted an enum, then just return this value.
        return (T)(object)(i);
    }
 
    public bool Equals(CodeStyleOption2<T>? other)
    {
        return other is not null
            && EqualityComparer<T>.Default.Equals(Value, other.Value)
            && Notification == other.Notification;
    }
 
    public override bool Equals(object? obj)
        => obj is CodeStyleOption2<T> option &&
           Equals(option);
 
    public override int GetHashCode()
        => unchecked((Notification.GetHashCode() * (int)0xA5555529) + EqualityComparer<T>.Default.GetHashCode(Value!));
}